/*
    Spherical Grid Volume Approximation: An algorithm for approximating the 
    volume of a compound object by keeping track of its members' positions in space.
    Copyright (C) 2009, Rodrigo Castano, FuDePAN

    This file is part of Spherical Grid Volume Approximation.

    Spherical Grid Volume Approximation is free software: you can redistribute 
    it and/or modify it under the terms of the GNU General Public License as published 
    by the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    Spherical Grid Volume Approximation is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.
*/


#include "grillado.h"

struct SphereInfo {
  CustomIdContainer vol_ids;
  CustomIdContainer non_vol_ids;
  inline unsigned int count() const;
  inline void clear();
};

inline unsigned int SphereInfo::count() const {
  return vol_ids.size() + non_vol_ids.size();
}

inline void SphereInfo::clear() {
  vol_ids.clear();
  non_vol_ids.clear();
}
enum GridAtomType
{
   Volumetric,
   NonVolumetric
};
template <GridAtomType Type>
class GridAtom {
  public:
		inline GridAtom(Coord a, Coord b, Coord c, CustomId id);
		friend class GrilladoCollision;
	private:
		const Coord x;
		const Coord y;
		const Coord z;
		const CustomId id;
};
typedef GridAtom<NonVolumetric> nonvolGridAtom;
typedef GridAtom<Volumetric> volGridAtom;
template <GridAtomType Type>
inline GridAtom<Type>::GridAtom(Coord x, Coord y, Coord z,CustomId id) : x(x), y(y), z(z), id(id) {}


typedef GridAtomType elemHandlerType;
template <elemHandlerType Type>
class elementHandler : public esferaId {
	public:
		inline elementHandler(GridCoord a, GridCoord b, GridCoord c, CustomId id);
		inline elementHandler(const elementHandler &s);
		inline CustomId get_CustomId() const;
	private:
		const CustomId id;
};
typedef elementHandler<NonVolumetric> nonvolElementHandler;
typedef elementHandler<Volumetric> volElementHandler;





class GrilladoCollision : public Grillado<SphereInfo> {
public:

	struct CustomIdNonExistent : public domain_error {
		CustomIdNonExistent()
			: domain_error( "The CustomId provided does not belong to the indicated sphere." ) {}

	};
	
	GrilladoCollision(size_t a, size_t b, size_t c, Length R, Length D) throw 
			(Grillado<SphereInfo>::radiusOrDistanceParamException, Grillado<SphereInfo>::sizeParamException, bad_alloc);

	~GrilladoCollision() ;
	
	inline volElementHandler add_element(const volGridAtom &a) ;

	
	
	nonvolElementHandler add_element( const nonvolGridAtom &a);
	
  	void get_customIDs_container(Coord x, Coord y, Coord z, CustomIdContainer &it) const ;
	
	
	inline void remove_element(const volElementHandler &id) throw (gridPositionException);
	
	
	inline void remove_element(const nonvolElementHandler &id) ;
	
private:

	
	void add_volumetric_element(const volElementHandler &id);
	
	inline void add_non_volumetric_element(const nonvolElementHandler &id);
	
	inline unsigned int show_quantity(GridCoord x, GridCoord y, GridCoord z) const;
	inline void reset_specific_sphere(GridCoord x, GridCoord y, GridCoord z) const;
	
	inline volElementHandler get_vol_element_handler(Coord x, Coord y, Coord z, CustomId id) const;
	inline nonvolElementHandler get_non_vol_element_handler(Coord x, Coord y, Coord z, CustomId id) const;
	
};


inline static void remove_CustomId(CustomIdContainer &container, CustomId i) throw (GrilladoCollision::CustomIdNonExistent) {
  if(container.back() == i) {
	  container.pop_back();
  }
  else {
    CustomIdContainer::iterator it = find(container.begin(),container.end(),i); 
    if(it==container.end()) {
      throw GrilladoCollision::CustomIdNonExistent();
    }
    else {
      container.erase(it);
    }
  }
};



template <elemHandlerType Type>
inline CustomId elementHandler<Type>::get_CustomId() const {
	return id;
}

// obtener_id debe estar antes que add_volumetric_element y sacar_esfera, para que se pueda satisfacer que sea inline.
inline volElementHandler GrilladoCollision::get_vol_element_handler(Coord x, Coord y, Coord z, CustomId id) const {
	GridCoord temp[3];
	calcular_coord(x,y,z,temp[0],temp[1],temp[2]);
	return volElementHandler(temp[0],temp[1],temp[2],id);
}


// obtener_id debe estar antes que add_volumetric_element y sacar_esfera, para que se pueda satisfacer que sea inline.
inline nonvolElementHandler GrilladoCollision::get_non_vol_element_handler(Coord x, Coord y, Coord z, CustomId id) const {
	GridCoord temp[3];
	calcular_coord(x,y,z,temp[0],temp[1],temp[2]);
	return nonvolElementHandler(temp[0],temp[1],temp[2],id);
}

inline volElementHandler GrilladoCollision::add_element(const volGridAtom &a) {
	volElementHandler result(get_vol_element_handler(a.x, a.y, a.z,a.id));
	add_volumetric_element(result);
	return result;
}




inline void GrilladoCollision::remove_element(const nonvolElementHandler &h) {
  const GridCoord coord_x = h.get_m();
	const GridCoord coord_y = h.get_n();
	const GridCoord coord_z = h.get_z();
  remove_CustomId(matriz[coord_x][coord_y][coord_z].non_vol_ids,h.get_CustomId());
}


inline void GrilladoCollision::remove_element(const volElementHandler &h) throw (gridPositionException){
  const GridCoord coord_x = h.get_m();
	const GridCoord coord_y = h.get_n();
	const GridCoord coord_z = h.get_z();
	if (matriz[coord_x][coord_y][coord_z].vol_ids.empty()) {
		throw gridPositionException();
	}
  remove_CustomId(matriz[coord_x][coord_y][coord_z].vol_ids,h.get_CustomId());
  if (matriz[coord_x][coord_y][coord_z].vol_ids.empty()) {
		reducir_vol_parcial(coord_x,coord_y,coord_z);
	}
}








template <elemHandlerType Type>
inline elementHandler<Type>::elementHandler(GridCoord input_v, GridCoord input_w, GridCoord input_z,CustomId input_id) : esferaId(input_v,input_w, input_z), id(input_id) {}

template <elemHandlerType Type>
inline elementHandler<Type>::elementHandler(const elementHandler &s) : esferaId( static_cast<esferaId>(s) ), id(s.get_CustomId()) {}


template <>
inline void Grillado<SphereInfo>::reset_specific_sphere(SphereInfo & sphere_data) const {
  sphere_data.clear();
}

template <>
inline unsigned int Grillado<SphereInfo>::is_empty_sphere(SphereInfo &sphere_data) { return sphere_data.vol_ids.empty(); }
